package com.start.workflow.core.configs;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.cfg.ProcessEngineConfigurator;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.db.DbSqlSessionFactory;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.interceptor.Session;
import org.activiti.engine.impl.util.ReflectUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Properties;

@Configuration
@ConditionalOnProperty(prefix = "spring.activiti", name = "data-base-type")
public class StartActProcessEngineConfigurator implements ProcessEngineConfigurator {

    private final StartActOtherDataBase startActDataBaseExt;

    public StartActProcessEngineConfigurator(StartActOtherDataBase startActDataBaseExt) {
        this.startActDataBaseExt = startActDataBaseExt;
    }

    /**
     * Called <b>before</b> any initialisation has been done. This can for example be useful to change configuration settings before anything that uses those properties is created.
     * <p>
     * Allows to tweak the process engine by passing the {@link ProcessEngineConfigurationImpl} which allows tweaking it programmatically.
     * <p>
     * An example is the jdbc url. When a {@link ProcessEngineConfigurator} instance wants to change it, it needs to do it in this method, or otherwise the datasource would already have been created
     * with the 'old' value for the jdbc url.
     *
     * @param processEngineConfiguration
     */
    @Override
    public void beforeInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
        if (StringUtils.isAllBlank(processEngineConfiguration.getDatabaseType())) {
            return;
        }
        if (!processEngineConfiguration.getDatabaseType().equals(startActDataBaseExt.getDataBaseType())) {
            return;
        }
        processEngineConfiguration.setDbSqlSessionFactory(new DbSqlSessionFactory() {
            @Override
            public Session openSession(CommandContext commandContext) {
                return new StartActDBSqlSession(startActDataBaseExt, this, commandContext.getEntityCache());
            }
        });
    }

    /**
     * Called when the engine boots up, before it is usable, but after the initialisation of internal objects is done.
     * <p>
     * Allows to tweak the process engine by passing the {@link ProcessEngineConfigurationImpl} which allows tweaking it programmatically.
     * <p>
     * An example is the ldap user/group manager, which is an addition to the engine. No default properties need to be overridden for this (otherwise the
     * {@link #beforeInit(ProcessEngineConfigurationImpl)} method should be used) so the logic contained in this method is executed after initialisation of the default objects.
     * <p>
     * Probably a better name would be 'afterInit' (cfr {@link #beforeInit(ProcessEngineConfigurationImpl)}), but not possible due to backwards compatibility.
     *
     * @param processEngineConfiguration
     */
    @Override
    public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
        SqlSessionFactory sqlSessionFactory;
        try {
            sqlSessionFactory = createSqlSessionFactory(processEngineConfiguration);
        } catch (IOException e) {
            throw new ActivitiException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
        }
        processEngineConfiguration.setSqlSessionFactory(sqlSessionFactory);
        //重新执行initDbSqlSessionFactory
        processEngineConfiguration.initDbSqlSessionFactory();
    }

    private SqlSessionFactory createSqlSessionFactory(ProcessEngineConfigurationImpl processEngineConfiguration) throws IOException {

        Environment environment = new Environment("default", processEngineConfiguration.getTransactionFactory(), processEngineConfiguration.getDataSource());
        Properties properties = new Properties();
        properties.put("prefix", processEngineConfiguration.getDatabaseTablePrefix());
        String wildcardEscapeClause = "";
        if ((processEngineConfiguration.getDatabaseWildcardEscapeCharacter() != null)
                && (processEngineConfiguration.getDatabaseWildcardEscapeCharacter().length() != 0)) {
            wildcardEscapeClause = " escape '" + processEngineConfiguration.getDatabaseWildcardEscapeCharacter()
                    + "'";
        }
        properties.put("wildcardEscapeClause", wildcardEscapeClause);
        // set default properties
        properties.put("limitBefore", "");
        properties.put("limitAfter", "");
        properties.put("limitBetween", "");
        properties.put("limitOuterJoinBetween", "");
        properties.put("limitBeforeNativeQuery", "");
        properties.put("orderBy", "order by ${orderByColumns}");
        properties.put("blobType", "BLOB");
        properties.put("boolValue", "TRUE");
        Reader reader = new InputStreamReader(processEngineConfiguration.getMyBatisXmlConfigurationStream());
        if (StringUtils.isNotBlank(startActDataBaseExt.getMapping())) {
            InputStream mappingInputStream = getResourceAsStream(startActDataBaseExt.getMapping());
            reader = new InputStreamReader(mappingInputStream);
        }
        String dbProperties = "org/activiti/db/properties/" + processEngineConfiguration.getDatabaseType();
        if (StringUtils.isNotBlank(startActDataBaseExt.getProperties())) {
            dbProperties = startActDataBaseExt.getProperties();
        }
        properties.load(getResourceAsStream(dbProperties + ".properties"));
        org.apache.ibatis.session.Configuration configuration = processEngineConfiguration.initMybatisConfiguration(environment, reader, properties);
        return new DefaultSqlSessionFactory(configuration);
    }

    private InputStream getResourceAsStream(String resource) {
        return ReflectUtil.getResourceAsStream(resource);
    }


    /**
     * When the {@link ProcessEngineConfigurator} instances are used, they are first ordered by this priority number (lowest to highest). If you have dependencies between
     * {@link ProcessEngineConfigurator} instances, use the priorities accordingly to order them as needed.
     */
    @Override
    public int getPriority() {
        return 0;
    }
}
